/*
 * einkservice.c
 *
 *  Created on: Apr 23, 2023
 *      Author: ycqia
 */

#include "CONFIG.h"
#include "einkservice.h"
#include "eink.h"
// Position of einkServiceHTNotify value in attribute array
#define EINKSERVICE_HTNOTIFY_VALUE_POS    11

/*********************************************************************
 * TYPEDEFS
 */

/*********************************************************************
 * GLOBAL VARIABLES
 */
// UUID: 0xFFF0
const uint8_t einkServiceServUUID[ATT_BT_UUID_SIZE] = {
		LO_UINT16(EINKSERVICE_SERV_UUID), HI_UINT16(EINKSERVICE_SERV_UUID) };

// eink status set UUID: 0xFFF1
const uint8_t einkServiceStatusUUID[ATT_BT_UUID_SIZE] = {
		LO_UINT16(EINKSERVICE_STATUS_UUID), HI_UINT16(EINKSERVICE_STATUS_UUID) };

// eink timestamp UUID: 0xFFF2
const uint8_t einkServiceTimeStampUUID[ATT_BT_UUID_SIZE] = {
		LO_UINT16(EINKSERVICE_TIMESTAMP_UUID), HI_UINT16(EINKSERVICE_TIMESTAMP_UUID) };

// eink config UUID: 0xFFF3
const uint8_t einkServiceCfgUUID[ATT_BT_UUID_SIZE] = {
		LO_UINT16(EINKSERVICE_CFG_UUID), HI_UINT16(EINKSERVICE_CFG_UUID) };

// temp humidity notify UUID: 0xFFF4
const uint8_t einkServiceHTNotifyUUID[ATT_BT_UUID_SIZE] = {
		LO_UINT16(EINKSERVICE_HTNOTIFY_UUID), HI_UINT16(EINKSERVICE_HTNOTIFY_UUID) };

// Characteristic 5 UUID: 0xFFF5
const uint8_t einkServicechar5UUID[ATT_BT_UUID_SIZE] = {
		LO_UINT16(EINKSERVICE_CHAR5_UUID), HI_UINT16(EINKSERVICE_CHAR5_UUID) };

/*********************************************************************
 * EXTERNAL VARIABLES
 */

/*********************************************************************
 * EXTERNAL FUNCTIONS
 */

/*********************************************************************
 * LOCAL VARIABLES
 */

static einkServiceCBs_t *einkService_AppCBs = NULL;

/*********************************************************************
 * Profile Attributes - variables
 */

// Simple Profile Service attribute
static const gattAttrType_t einkServiceService =
		{ ATT_BT_UUID_SIZE, einkServiceServUUID };

static uint8_t einkServiceStatusProps = GATT_PROP_READ;
static uint8_t einkServiceStatus[EINKSERVICE_STATUS_LEN] = { 0 };
static uint8_t einkServiceStatusUserDesp[] = "eink status\0";

static uint8_t einkServiceTimeStampProps = GATT_PROP_READ | GATT_PROP_WRITE;
static uint8_t einkServiceTimeStamp[EINKSERVICE_TIMESTAMP_LEN] = { 0 };
static uint8_t einkServiceTimeStampUserDesp[] = "eink timestamp set\0";

// Simple Profile Characteristic 3 Properties
static uint8_t einkServiceCfgProps = GATT_PROP_READ | GATT_PROP_WRITE;
static uint8_t einkServiceCfg[EINKSERVICE_CFG_LEN] = { 0 };
static uint8_t einkServiceCfgUserDesp[] = "eink config\0";

static uint8_t einkServiceHTNotifyProps = GATT_PROP_NOTIFY;
static uint8_t einkServiceHTNotify[EINKSERVICE_HTNOTIFY_LEN] = { 0 };
static gattCharCfg_t einkServiceHTNotifyConfig[PERIPHERAL_MAX_CONNECTION];
static uint8_t einkServiceHTNotifyUserDesp[] = "temp humidity notify\0";

static uint8_t einkServiceChar5Props = GATT_PROP_READ;
static uint8_t einkServiceChar5[EINKSERVICE_CHAR5_LEN] = { 0 };
static uint8_t einkServiceChar5UserDesp[] = "Characteristic 5\0";

/*********************************************************************
 * Profile Attributes - Table
 */

static gattAttribute_t einkServiceAttrTbl[] =
		{
				// Simple Profile Service
				{ { ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */GATT_PERMIT_READ, /* permissions */0, /* handle */(uint8_t *) &einkServiceService /* pValue */},

// Characteristic 1 Declaration
				{ { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &einkServiceStatusProps },
				{ { ATT_BT_UUID_SIZE, einkServiceStatusUUID }, GATT_PERMIT_READ, 0, einkServiceStatus },
				{ { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, einkServiceStatusUserDesp },

// Characteristic 2 Declaration
				{ { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &einkServiceTimeStampProps },
				{ { ATT_BT_UUID_SIZE, einkServiceTimeStampUUID }, GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, einkServiceTimeStamp },
				{ { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, einkServiceTimeStampUserDesp },

// Characteristic 3 Declaration
				{ { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &einkServiceCfgProps },
				{ { ATT_BT_UUID_SIZE, einkServiceCfgUUID }, GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, einkServiceCfg },
				{ { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, einkServiceCfgUserDesp },

// Characteristic 4 Declaration
				{ { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &einkServiceHTNotifyProps },
				{ { ATT_BT_UUID_SIZE, einkServiceHTNotifyUUID }, 0, 0, einkServiceHTNotify },
				{ { ATT_BT_UUID_SIZE, clientCharCfgUUID }, GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, (uint8_t *) einkServiceHTNotifyConfig },
				{ { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, einkServiceHTNotifyUserDesp },

// Characteristic 5 Declaration
				{ { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &einkServiceChar5Props },
				{ { ATT_BT_UUID_SIZE, einkServicechar5UUID }, GATT_PERMIT_READ, 0, einkServiceChar5 },
				{ { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, einkServiceChar5UserDesp },
		};
/*********************************************************************
 * LOCAL FUNCTIONS
 */
static bStatus_t einkService_ReadAttrCB(uint16_t connHandle, gattAttribute_t *pAttr,
		uint8_t *pValue, uint16_t *pLen, uint16_t offset, uint16_t maxLen, uint8_t method);
static bStatus_t einkService_WriteAttrCB(uint16_t connHandle, gattAttribute_t *pAttr,
		uint8_t *pValue, uint16_t len, uint16_t offset, uint8_t method);

static void einkService_HandleConnStatusCB(uint16_t connHandle, uint8_t changeType);
static bStatus_t einkService_ValidateLen(uint16_t tempUUID, uint16_t lenUUID, uint16_t len, uint16_t offset);
/*********************************************************************
 * PROFILE CALLBACKS
 */
// Simple Profile Service Callbacks
gattServiceCBs_t einkServiceCBs = {
		einkService_ReadAttrCB,  // Read callback function pointer
		einkService_WriteAttrCB, // Write callback function pointer
		NULL                       // Authorization callback function pointer
		};
/*********************************************************************
 * @fn      EinkService_AddService
 *
 * @brief   Initializes the Simple Profile service by registering
 *          GATT attributes with the GATT server.
 *
 * @param   services - services to add. This is a bit map and can
 *                     contain more than one service.
 *
 * @return  Success or Failure
 */
bStatus_t EinkService_AddService(uint32_t services)
{
	uint8_t status = SUCCESS;

	// Initialize Client Characteristic Configuration attributes
	GATTServApp_InitCharCfg(INVALID_CONNHANDLE, einkServiceHTNotifyConfig);

	// Register with Link DB to receive link status change callback
	linkDB_Register(einkService_HandleConnStatusCB);

	if (services & EINKSERVICE_SERVICE)
	{
		// Register GATT attribute list and CBs with GATT Server App
		status = GATTServApp_RegisterService(einkServiceAttrTbl,
				GATT_NUM_ATTRS(einkServiceAttrTbl),
				GATT_MAX_ENCRYPT_KEY_SIZE,
				&einkServiceCBs);
	}

	return (status);
}

/*********************************************************************
 * @fn      EinkService_RegisterAppCBs
 *
 * @brief   Registers the application callback function. Only call
 *          this function once.
 *
 * @param   callbacks - pointer to application callbacks.
 *
 * @return  SUCCESS or bleAlreadyInRequestedMode
 */
bStatus_t EinkService_RegisterAppCBs(einkServiceCBs_t *appCallbacks)
{
	if (appCallbacks)
	{
		einkService_AppCBs = appCallbacks;

		return (SUCCESS);
	}
	else
	{
		return (bleAlreadyInRequestedMode);
	}
}

/*********************************************************************
 * @fn      EinkService_SetParameter
 *
 * @brief   Set a Simple Profile parameter.
 *
 * @param   param - Profile parameter ID
 * @param   len - length of data to right
 * @param   value - pointer to data to write.  This is dependent on
 *          the parameter ID and WILL be cast to the appropriate
 *          data type (example: data type of uint16_t will be cast to
 *          uint16_t pointer).
 *
 * @return  bStatus_t
 */
bStatus_t EinkService_SetParameter(uint8_t param, uint8_t len, void *value)
{
	bStatus_t ret = SUCCESS;
	switch (param)
	{
	case EINKSERVICE_STATUS:
		if (len == EINKSERVICE_STATUS_LEN)
		{
			tmos_memcpy(einkServiceStatus, value, EINKSERVICE_STATUS_LEN);
		}
		else
		{
			ret = bleInvalidRange;
		}
		break;

	case EINKSERVICE_TIMESTAMP:
		if (len == EINKSERVICE_TIMESTAMP_LEN)
		{
			tmos_memcpy(einkServiceTimeStamp, value, EINKSERVICE_TIMESTAMP_LEN);
		}
		else
		{
			ret = bleInvalidRange;
		}
		break;

	case EINKSERVICE_CFG:
		if (len == EINKSERVICE_CFG_LEN)
		{
			tmos_memcpy(einkServiceCfg, value, EINKSERVICE_CFG_LEN);
		}
		else
		{
			ret = bleInvalidRange;
		}
		break;

	case EINKSERVICE_HTNOTIFY:
		if (len == EINKSERVICE_HTNOTIFY_LEN)
		{
			tmos_memcpy(einkServiceHTNotify, value, EINKSERVICE_HTNOTIFY_LEN);
		}
		else
		{
			ret = bleInvalidRange;
		}
		break;

	case EINKSERVICE_CHAR5:
		if (len == EINKSERVICE_CHAR5_LEN)
		{
			tmos_memcpy(einkServiceChar5, value, EINKSERVICE_CHAR5_LEN);
		}
		else
		{
			ret = bleInvalidRange;
		}
		break;

	default:
		ret = INVALIDPARAMETER;
		break;
	}

	return (ret);
}

/*********************************************************************
 * @fn      EinkService_GetParameter
 *
 * @brief   Get a Simple Profile parameter.
 *
 * @param   param - Profile parameter ID
 * @param   value - pointer to data to put.  This is dependent on
 *          the parameter ID and WILL be cast to the appropriate
 *          data type (example: data type of uint16_t will be cast to
 *          uint16_t pointer).
 *
 * @return  bStatus_t
 */
bStatus_t EinkService_GetParameter(uint8_t param, void *value)
{
	bStatus_t ret = SUCCESS;
	switch (param)
	{
	case EINKSERVICE_STATUS:
		tmos_memcpy(value, einkServiceStatus, EINKSERVICE_STATUS_LEN);
		break;

	case EINKSERVICE_TIMESTAMP:
		tmos_memcpy(value, einkServiceTimeStamp, EINKSERVICE_TIMESTAMP_LEN);
		break;

	case EINKSERVICE_CFG:
		tmos_memcpy(value, einkServiceCfg, EINKSERVICE_CFG_LEN);
		break;

	case EINKSERVICE_HTNOTIFY:
		tmos_memcpy(value, einkServiceHTNotify, EINKSERVICE_HTNOTIFY_LEN);
		break;

	case EINKSERVICE_CHAR5:
		tmos_memcpy(value, einkServiceChar5, EINKSERVICE_CHAR5_LEN);
		break;

	default:
		ret = INVALIDPARAMETER;
		break;
	}

	return (ret);
}

/*********************************************************************
 * @fn          einkService_Notify
 *
 * @brief       Send a notification containing a heart rate
 *              measurement.
 *
 * @param       connHandle - connection handle
 * @param       pNoti - pointer to notification structure
 *
 * @return      Success or Failure
 */
bStatus_t einkService_Notify(uint16_t connHandle, attHandleValueNoti_t *pNoti)
{
	uint16_t value = GATTServApp_ReadCharCfg(connHandle, einkServiceHTNotifyConfig);

	// If notifications enabled
	if (value & GATT_CLIENT_CFG_NOTIFY)
	{
		// Set the handle
		pNoti->handle = einkServiceAttrTbl[EINKSERVICE_HTNOTIFY_VALUE_POS].handle;

		// Send the notification
		return GATT_Notification(connHandle, pNoti, FALSE);
	}
	return bleIncorrectMode;
}

/*********************************************************************
 * @fn          einkService_ReadAttrCB
 *
 * @brief       Read an attribute.
 *
 * @param       connHandle - connection message was received on
 * @param       pAttr - pointer to attribute
 * @param       pValue - pointer to data to be read
 * @param       pLen - length of data to be read
 * @param       offset - offset of the first octet to be read
 * @param       maxLen - maximum length of data to be read
 *
 * @return      Success or Failure
 */
static bStatus_t einkService_ReadAttrCB(uint16_t connHandle, gattAttribute_t *pAttr,
		uint8_t *pValue, uint16_t *pLen, uint16_t offset, uint16_t maxLen, uint8_t method)
{
	bStatus_t status = SUCCESS;

	// Make sure it's not a blob operation (no attributes in the profile are long)
	if (offset > 0)
			{
		return (ATT_ERR_ATTR_NOT_LONG);
	}

	if (pAttr->type.len == ATT_BT_UUID_SIZE)
	{
		// 16-bit UUID
		uint16_t uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
		switch (uuid)
		{
		case EINKSERVICE_STATUS_UUID:
			if (maxLen > EINKSERVICE_STATUS_LEN)
			{
				*pLen = EINKSERVICE_STATUS_LEN;
			}
			else
			{
				*pLen = maxLen;
			}
			tmos_memcpy(pValue, &einkStatus.einkIdx, *pLen);
			break;

		case EINKSERVICE_TIMESTAMP_UUID:
			if (maxLen > EINKSERVICE_TIMESTAMP_LEN)
			{
				*pLen = EINKSERVICE_TIMESTAMP_LEN;
			}
			else
			{
				*pLen = maxLen;
			}
			tmos_memcpy(pValue, &einkStatus.timestamp, *pLen);
			break;

		case EINKSERVICE_CFG_UUID:
			if (maxLen > EINKSERVICE_CFG_LEN)
			{
				*pLen = EINKSERVICE_CFG_LEN;
			}
			else
			{
				*pLen = maxLen;
			}
			tmos_memcpy(pValue, &flashSavedCFG, *pLen);
			break;

		case EINKSERVICE_HTNOTIFY_UUID:
			if (maxLen > EINKSERVICE_HTNOTIFY_LEN)
			{
				*pLen = EINKSERVICE_HTNOTIFY_LEN;
			}
			else
			{
				*pLen = maxLen;
			}
			tmos_memcpy(pValue, pAttr->pValue, *pLen);
			break;

		case EINKSERVICE_CHAR5_UUID:
			if (maxLen > EINKSERVICE_CHAR5_LEN)
			{
				*pLen = EINKSERVICE_CHAR5_LEN;
			}
			else
			{
				*pLen = maxLen;
			}
			tmos_memcpy(pValue, pAttr->pValue, *pLen);
			break;

		default:
			// Should never get here! (characteristics 3 and 4 do not have read permissions)
			*pLen = 0;
			status = ATT_ERR_ATTR_NOT_FOUND;
			break;
		}
	}
	else
	{
		// 128-bit UUID
		*pLen = 0;
		status = ATT_ERR_INVALID_HANDLE;
	}

	return (status);
}

static bStatus_t einkService_ValidateLen(uint16_t tempUUID, uint16_t lenUUID, uint16_t len, uint16_t offset) {
	bStatus_t status = SUCCESS;
	if (offset == 0)
			{
		if (len > lenUUID)
				{
			status = ATT_ERR_INVALID_VALUE_SIZE;
		}
	}
	else
	{
		status = ATT_ERR_ATTR_NOT_LONG;
	}
	return status;
}
/*********************************************************************
 * @fn      einkService_WriteAttrCB
 *
 * @brief   Validate attribute data prior to a write operation
 *
 * @param   connHandle - connection message was received on
 * @param   pAttr - pointer to attribute
 * @param   pValue - pointer to data to be written
 * @param   len - length of data
 * @param   offset - offset of the first octet to be written
 *
 * @return  Success or Failure
 */
static bStatus_t einkService_WriteAttrCB(uint16_t connHandle, gattAttribute_t *pAttr,
		uint8_t *pValue, uint16_t len, uint16_t offset, uint8_t method)
{
	bStatus_t status = SUCCESS;
	uint8_t notifyApp = 0xFF;

	// If attribute permissions require authorization to write, return error
	if (gattPermitAuthorWrite(pAttr->permissions))
			{
		// Insufficient authorization
		return (ATT_ERR_INSUFFICIENT_AUTHOR);
	}

	if (pAttr->type.len == ATT_BT_UUID_SIZE)
	{
		// 16-bit UUID
		uint16_t uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
		switch (uuid)
		{
		case EINKSERVICE_TIMESTAMP_UUID:
			status = einkService_ValidateLen(EINKSERVICE_TIMESTAMP_UUID, EINKSERVICE_TIMESTAMP_LEN, len, offset);

			//Write the value
			if (status == SUCCESS)
			{
				tmos_memcpy(pAttr->pValue, pValue, EINKSERVICE_TIMESTAMP_LEN);
				notifyApp = EINKSERVICE_TIMESTAMP;
			}
			break;

		case EINKSERVICE_CFG_UUID:
			status = einkService_ValidateLen(EINKSERVICE_CFG_UUID, EINKSERVICE_CFG_LEN, len, offset);

			//Write the value
			if (status == SUCCESS)
			{
				tmos_memcpy(pAttr->pValue, pValue, EINKSERVICE_CFG_LEN);
				notifyApp = EINKSERVICE_CFG;
			}
			break;

		case GATT_CLIENT_CHAR_CFG_UUID:
			status = GATTServApp_ProcessCCCWriteReq(connHandle, pAttr, pValue, len,
					offset, GATT_CLIENT_CFG_NOTIFY);
			break;

		default:
			// Should never get here! (characteristics 2 and 4 do not have write permissions)
			status = ATT_ERR_ATTR_NOT_FOUND;
			break;
		}
	}
	else
	{
		// 128-bit UUID
		status = ATT_ERR_INVALID_HANDLE;
	}

	// If a charactersitic value changed then callback function to notify application of change
	if ((notifyApp != 0xFF) && einkService_AppCBs && einkService_AppCBs->pfnEinkServiceChange)
			{
		einkService_AppCBs->pfnEinkServiceChange(notifyApp, pValue, len);
	}

	return (status);
}

/*********************************************************************
 * @fn          einkService_HandleConnStatusCB
 *
 * @brief       Simple Profile link status change handler function.
 *
 * @param       connHandle - connection handle
 * @param       changeType - type of change
 *
 * @return      none
 */
static void einkService_HandleConnStatusCB(uint16_t connHandle, uint8_t changeType)
{
	// Make sure this is not loopback connection
	if (connHandle != LOOPBACK_CONNHANDLE)
	{
		// Reset Client Char Config if connection has dropped
		if ((changeType == LINKDB_STATUS_UPDATE_REMOVED) ||
				((changeType == LINKDB_STATUS_UPDATE_STATEFLAGS) &&
						(!linkDB_Up(connHandle))))
				{
			GATTServApp_InitCharCfg(connHandle, einkServiceHTNotifyConfig);
		}
	}
}

